﻿//This is complete listing of my assigment
////////////////////////////////
using System;
using System.Collections.Generic;

public class Matrix
{
    private int row_matrix;    //number of rows for matrix
    private int column_matrix; //number of colums for matrix
    private float[,] matrix;   //holds values of matrix itself
    private string matname;

    public string matName
    {
        get { return matname; }
        set { matname = value; }
    }

    ///////////////////////////////////////////////Constructors BEGIN
    //set matrix size to 0*0
    public Matrix()
    {
        matrix = new float[0, 0];
        Console.WriteLine("Contructor which sets matrix size to 0*0 executed.");
    }

    //create quadratic matrix according to parameters passed to this constructor
    public Matrix(int quadratic_size)
    {
        row_matrix = column_matrix = quadratic_size;
        matrix = new float[row_matrix, column_matrix];
        Console.WriteLine("Contructor which sets matrix size to quadratic size {0}*{1} executed.", row_matrix, column_matrix);
    }

    //create n*m matrix according to parameters passed to this constructor
    public Matrix(int row, int column)
    {
        row_matrix = row;
        column_matrix = column;
        matrix = new float[row_matrix, column_matrix];
        Console.WriteLine("Contructor which sets matrix size {0}*{1} executed.", row_matrix, column_matrix);
    }

    //create n*m matrix and fill it with data passed to this constructor
    public Matrix(float[,] double_array)
    {
        matrix = double_array;
        row_matrix = matrix.GetLength(0);
        column_matrix = matrix.GetLength(1);
        Console.WriteLine("Contructor which sets matrix size {0}*{1} and fill it with initial data executed.", row_matrix, column_matrix);
    }

    /////////////////////////////////////////Constructors END

    /////////////////////////////////////////Methods BEGIN
    //returns total number of rows
    public int countRows()
    {
        return row_matrix;
    }

    //--------------------------------------------
    //returns total number of columns
    public int countColumns()
    {
        return column_matrix;
    }

    //--------------------------------------------
    //returns value of an element for a given row and column of matrix
    public float readElement(int row, int colummn)
    {
        return matrix[row, colummn];
    }

    //--------------------------------------------
    //sets value of an element for a given row and column of matrix
    public void setElement(float value, int row, int column)
    {
        matrix[row, column] = value;
    }

    //--------------------------------------------
    //returns transposed matrix matrixT
    //method does not change original matrix, therefore original matrix is preserved
    public Matrix transMatrix()
    {
        //first swap number of rows with number of columns
        Matrix matrixT = new Matrix(column_matrix, row_matrix);

        for (int row = 0; row < row_matrix; row++)
        {
            for (int column = 0; column < column_matrix; column++)
            {
                float value = this.readElement(row, column);
                matrixT.setElement(value, column, row);
            }
        }

        return matrixT;
    }

    //--------------------------------------------
    //returns calculated value of determinat for square matrix
    public float MatrixDeterminant()
    {
        int[] perm;
        int toggle;
        float result = 0;

        //check if matrix is square matrix if not, abort
        if (this.countRows() != this.countColumns())
        {
            Console.WriteLine("Attempt to calculate value of determinant for non-square matrix");
            return result;
        }

        float[,] lum = MatrixDecompose(matrix, out perm, out toggle);

        //if (lum == null)
        //    throw new Exception("Unable to compute MatrixDeterminant");
        if (lum == null)
        {
            Console.WriteLine("Unable to compute MatrixDeterminant");
            return result;
        }

        result = toggle;
        for (int i = 0; i < lum.GetLength(0); ++i)
            result *= lum[i, i];

        return result;
    }

    //------------------------------------------------
    //Doolittle LUP decomposition with partial pivoting

    public float[,] MatrixDecompose(float[,] matrix, out int[] perm, out int toggle)
    {
        // Doolittle LUP decomposition with partial pivoting.
        // rerturns: result is L (with 1s on diagonal) and U; perm holds row permutations; toggle is +1 or -1 (even or odd)
        int rows = matrix.GetLength(0);
        int cols = matrix.GetLength(1);

        //Check if matrix is square
        if (rows != cols)
            throw new Exception("Attempt to MatrixDecompose a non-square mattrix");

        float[,] result = MatrixDuplicate(matrix); // make a copy of the input matrix

        perm = new int[rows]; // set up row permutation result
        for (int i = 0; i < rows; ++i) { perm[i] = i; } // i are rows counter

        toggle = 1; // toggle tracks row swaps. +1 -> even, -1 -> odd. used by MatrixDeterminant

        for (int j = 0; j < rows - 1; ++j) // each column, j is counter for coulmns
        {
            float colMax = Math.Abs(result[j, j]); // find largest value in col j
            int pRow = j;
            for (int i = j + 1; i < rows; ++i)
            {
                if (result[i, j] > colMax)
                {
                    colMax = result[i, j];
                    pRow = i;
                }
            }

            if (pRow != j) // if largest value not on pivot, swap rows
            {
                float[] rowPtr = new float[result.GetLength(1)];

                //in order to preserve value of j new variable k for counter is declared
                //rowPtr[] is a 1D array that contains all the elements on a single row of the matrix
                //there has to be a loop over the columns to transfer the values
                //from the 2D array to the 1D rowPtr array.

                //----tranfer 2D array to 1D array BEGIN
                for (int k = 0; k < result.GetLength(1); k++)
                {
                    rowPtr[k] = result[pRow, k];
                }

                for (int k = 0; k < result.GetLength(1); k++)
                {
                    result[pRow, k] = result[j, k];
                }

                for (int k = 0; k < result.GetLength(1); k++)
                {
                    result[j, k] = rowPtr[k];
                }

                //----tranfer 2D array to 1D array END

                int tmp = perm[pRow]; // and swap perm info
                perm[pRow] = perm[j];
                perm[j] = tmp;

                toggle = -toggle; // adjust the row-swap toggle
            }

            if (Math.Abs(result[j, j]) < 1.0E-20) // if diagonal after swap is zero . . .
                return null; // consider a throw

            for (int i = j + 1; i < rows; ++i)
            {
                result[i, j] /= result[j, j];
                for (int k = j + 1; k < rows; ++k)
                {
                    result[i, k] -= result[i, j] * result[j, k];
                }
            }
        } // main j column loop

        return result;
    }

    // -----------------------------------------------------------------------------------------
    public float[,] MatrixDuplicate(float[,] matrix)
    {
        // allocates/creates a duplicate of a matrix. assumes matrix is not null.
        float[,] result = MatrixCreate(matrix.GetLength(0), matrix.GetLength(1));
        for (int i = 0; i < matrix.GetLength(0); ++i) // copy the values
            for (int j = 0; j < matrix.GetLength(1); ++j)
                result[i, j] = matrix[i, j];
        return result;
    }

    //--------------------------------------------------------------------------
    public float[,] MatrixCreate(int rows, int cols)
    {
        // allocates/creates a matrix initialized to all 0.0. assume rows and cols > 0
        // do error checking here
        float[,] result = new float[rows, cols];
        return result;
    }

    //------------------------------------------------------------------------------
    //Matrix Inverse calculation
    public float[,] MatrixInverse()
    {
        int n = matrix.GetLength(0);
        float[,] result = MatrixDuplicate(matrix);

        //check if matrix is square matrix if not, abort
        if (this.countRows() != this.countColumns())
        {
            Console.WriteLine("Attempt to calculate value of determinant for non-square matrix");
            return result;
        }

        int[] perm;
        int toggle;
        float[,] lum = MatrixDecompose(matrix, out perm, out toggle);

        if (lum == null)
        {
            //throw new Exception("Unable to compute inverse");
            return result;
        }

        float[] b = new float[n];
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < n; ++j)
            {
                if (i == perm[j])
                    b[j] = 1.0f;
                else
                    b[j] = 0.0f;
            }

            float[] x = HelperSolve(lum, b); //

            for (int j = 0; j < n; ++j)
                result[j, i] = x[j];
        }
        return result;
    }

    // -----------------------------------------------------------------------------
    // helper
    public float[] HelperSolve(float[,] luMatrix, float[] b)
    {
        // before calling this helper, permute b using the perm array from MatrixDecompose that generated luMatrix
        int n = luMatrix.GetLength(0);
        float[] x = new float[n];
        b.CopyTo(x, 0);

        for (int i = 1; i < n; ++i)
        {
            float sum = x[i];
            for (int j = 0; j < i; ++j)
                sum -= luMatrix[i, j] * x[j];
            x[i] = sum;
        }

        x[n - 1] /= luMatrix[(n - 1), (n - 1)];
        for (int i = n - 2; i >= 0; --i)
        {
            float sum = x[i];
            for (int j = i + 1; j < n; ++j)
                sum -= luMatrix[i, j] * x[j];
            x[i] = sum / luMatrix[i, i];
        }

        return x;
    }

    //---------------------------------------------------------------------------

    /////////////////////////////////////////////Methods END
}

internal class Program
{
    private static void Main(string[] args)
    {
        //Create a collection to help iterate later
        List<Matrix> list_matrix = new List<Matrix>();

        //Test if all overloads work as they should.
        Matrix mat01 = new Matrix();
        mat01.matName = "mat01";
        list_matrix.Add(mat01);

        Matrix mat02 = new Matrix(2);
        mat02.matName = "mat02";
        list_matrix.Add(mat02);

        Matrix mat03 = new Matrix(4, 2);
        mat03.matName = "mat03";
        list_matrix.Add(mat03);

        Matrix mat04 = new Matrix(new[,]
        {
            { 1f, 2f, 3f, },
            { 4f, 5f, 6f }
        });
        mat04.matName = "mat04";
        list_matrix.Add(mat04);

        Matrix mat05 = new Matrix(new[,]
        {
            { 1f, 2f, 3f, },
            { 4f, 5f, 6f },
            { 7f, 8f, 9f }
        });
        mat05.matName = "mat05";
        list_matrix.Add(mat05);

        Matrix mat06 = new Matrix(new[,]
        {
            {1.0f, 2.0f, 1.0f, 2.0f, 3.0f},
            {2.0f, 1.0f, 2.0f, 2.0f, 1.0f},
            {3.0f, 1.0f, 3.0f, 1.0f, 2.0f},
            {1.0f, 2.0f, 4.0f, 3.0f, 2.0f},
            {2.0f, 2.0f, 1.0f, 2.0f, 1.0f},
        });

        mat06.matName = "mat06";
        list_matrix.Add(mat06);

        //////////////////////////////////////////////
        //Test if all methods work as they should.
        Console.WriteLine("----------------------------------------");
        Console.WriteLine("Test if all methods work as they should.");

        //------------foreach for all elements in list START
        foreach (Matrix mat in list_matrix)
        {
            //Invoking counter of rows & columns
            Console.WriteLine("----------------------------------------");
            Console.Write("Matrix {0} has: {1} Rows and {2} Columns\n",
                mat.matName, mat.countRows(), mat.countColumns());

            Console.Write("Elements of matrix in a flattened way are: ");

            for (int row = 0; row < mat.countRows(); row++)
            {
                for (int column = 0; column < mat.countColumns(); column++)
                {
                    Console.Write(mat.readElement(row, column));

                    //Do not add "," if it is last element
                    if (((row + 1) != mat.countRows()) || ((column + 1) != mat.countColumns()))
                        Console.Write(",");
                }
            }

            Console.WriteLine("\nElements of matrix row by row are as follows:");

            for (int row = 0; row < mat.countRows(); row++)
            {
                Console.Write("{");
                for (int column = 0; column < mat.countColumns(); column++)
                {
                    Console.Write(mat.readElement(row, column));

                    //Do not add "," if it is last element in a row
                    if (((row) != mat.countRows()) && ((column + 1) != mat.countColumns()))
                        Console.Write(",");
                }
                Console.WriteLine("}");
            }

            //Console.WriteLine("\n");

            ///////////////////////mat.transMatrix
            Console.WriteLine("\nElements of transposed matrix row by row are as follows:");
            Matrix matT = mat.transMatrix();

            Console.WriteLine("Number of rows {0} columns {1}", matT.countRows(), matT.countColumns());

            for (int row = 0; row < matT.countRows(); row++)
            {
                Console.Write("{");
                for (int column = 0; column < matT.countColumns(); column++)
                {
                    Console.Write(matT.readElement(row, column));

                    //Do not add "," if it is last element in a row
                    if (((row) != matT.countRows()) && ((column + 1) != matT.countColumns()))
                        Console.Write(",");
                }
                Console.WriteLine("}");
            }
            Console.WriteLine("\n");

            //-----call for determinant calculation
            double determinant = mat.MatrixDeterminant();
            Console.Write("Calculated value of determinat is: {0}\n", determinant);

            //-----call for matrix inverse

            //Matrix inverse = mat06.MatrixInverse();

            Console.WriteLine("Inverse matrix is");
            float[,] matI = mat.MatrixInverse();

            for (int i = 0; i < matI.GetLength(0); i++)
            {
                for (int j = 0; j < matI.GetLength(1); j++)
                {
                    Console.Write("{0}\t", matI[i, j]);
                }
                Console.WriteLine();
            }
        }

        //------------foreach for all elements in list END

        ;
    }
}